// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: gekim

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::base::feed_util::Index;
using namespace coin::proto;

class PrexBaseFeedUpdateParser {
 public:
  static bool
  ParseBook(BookBuilder* book_builder_, int64_t ts, const rapidjson::Value::Array& transactions) {
    auto tx = book_builder_->NewTransaction(ts);
    for (auto& books_json : transactions) {
      const std::string& type = books_json["actionType"].GetString();
      if (type == "SNAPSHOT") tx.Clear();
      if (type == "UPDATE" || type == "INSERT" || type == "SNAPSHOT") {
        for (const auto& book : books_json["rows"].GetArray()) {
          auto price = stod(book["price"].GetString());
          auto qty = stod(book["qty"].GetString());
          if (std::string_view(book["side"].GetString()) == "ASK") {
            tx.UpdateAsk(price, qty);
          } else if (std::string_view(book["side"].GetString()) == "BID") {
            tx.UpdateBid(price, qty);
          } else {
            throw std::runtime_error(
                "[Prex] unknown side in ParseBook:" + std::string(book["side"].GetString()));
            return false;
          }
          if (book.HasMember("tx_timestamp")) {
            tx.UpdateTimestamp(stod(book["tx_timestamp"].GetString()));
          }
        }
      } else if (type == "DELETE") {
        for (const auto& book : books_json["rows"].GetArray()) {
          auto price = stod(book["price"].GetString());
          if (std::string_view(book["side"].GetString()) == "ASK") {
            tx.UpdateAsk(price, 0LL);
          } else if (std::string_view(book["side"].GetString()) == "BID") {
            tx.UpdateBid(price, 0LL);
          } else {
            throw std::runtime_error(
                "[Prex] unknown side in ParseBook:" + std::string(book["side"].GetString()));
            return false;
          }
          if (book.HasMember("tx_timestamp")) {
            tx.UpdateTimestamp(stod(book["tx_timestamp"].GetString()));
          }
        }
      } else {
        THROW() << "[Prex Feed Processor] unkown book type: " << type;
      }
    }
    return true;
  }

  static void ParseTrade(const rapidjson::Value& trade_json, Trade* trade) {
    coin2::exchange::feed::TradeSide side = ParseTradeSide(trade_json["side"].GetString());
    trade->side = side;
    trade->timestamp = stoull(trade_json["timestamp"].GetString());
    trade->price = stod(trade_json["price"].GetString());
    trade->fill_qty = stod(trade_json["qty"].GetString());
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(const std::string& side) {
    if (side == "BUY") {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == "SELL") {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else {
      throw std::runtime_error("[Prex] unknown side: " + side);
      return coin2::exchange::feed::TRADE_NO_SIDE;
    }
  }

  static bool ParseMarkPrice(
      const rapidjson::Value& instrument_json,
      MarkPrice* mark_price,
      int64_t timestamp) {
    if (instrument_json.HasMember("markPrice")) {
      mark_price->timestamp = timestamp;
      mark_price->mark_price = stod(instrument_json["markPrice"].GetString());
      return true;
    } else {
      return false;
    }
  }

  static bool ParseFundingRate(
      const rapidjson::Value& instrument_json,
      FundingRate* fuding_rate,
      int64_t timestamp) {
    bool success = false;
    if (instrument_json.HasMember("fundingTime")) {
      fuding_rate->timestamp = timestamp;
      fuding_rate->funding_time = stod(instrument_json["fundingTime"].GetString());
      if (fuding_rate->funding_rate != 0){
        success = true;
      }
    }
    if (instrument_json.HasMember("fundingRate")) {
      fuding_rate->timestamp = timestamp;
      fuding_rate->funding_rate = stod(instrument_json["fundingRate"].GetString());
      success = true;
    }
    return success;
  }

  static void ParseIndex(
      const rapidjson::Value& instrument_json,
      Index* index,
      int64_t timestamp) {
    index->timestamp = timestamp;
    index->price = stod(instrument_json["lastPrice"].GetString());
  }
};

}  // namespace impl

using impl::PrexBaseFeedUpdateParser;
